#include "mkvmuxfilter.hpp"
#include "mkvstream.hpp"
#include <vfwmsgs.h>
#include <cassert>

namespace MkvMux
{


Inpin::Inpin(Filter* p, const wchar_t* id) : 
    Pin(p, id, PINDIR_INPUT),
    m_pStream(0)
{
}


void Inpin::Init()
{
   m_bFlush = false;
   m_bEndOfStream = false;

   assert(m_pStream == 0);

   if (m_connection == 0)
      return;

   const HRESULT hr = OnInit();
   assert(SUCCEEDED(hr));
   assert(m_pStream);
   hr;
}


HRESULT Inpin::QueryInterface(const IID& iid, void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
    
    if (iid == __uuidof(IUnknown))
        pUnk = static_cast<IPin*>(this);
        
    else if (iid == __uuidof(IPin))
        pUnk = static_cast<IPin*>(this);
        
    else if (iid == __uuidof(IMemInputPin))
        pUnk = static_cast<IMemInputPin*>(this);
        
    else
    {
        pUnk = 0;
        return E_NOINTERFACE;
    }
    
    pUnk->AddRef();
    return S_OK;
}
    

ULONG Inpin::AddRef()
{
    return m_pFilter->AddRef();
}


ULONG Inpin::Release()
{
    return m_pFilter->Release();
}


HRESULT Inpin::Connect(IPin*, const AM_MEDIA_TYPE*)
{
    return E_UNEXPECTED;  //for output pins only
}


HRESULT Inpin::QueryInternalConnections( 
    IPin** pa,
    ULONG* pn)
{
    if (pn == 0)
        return E_POINTER;
        
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (*pn == 0)
    {
        if (pa == 0)  //query for required number
        {
            *pn = 1;
            return S_OK;
        }
        
        return S_FALSE;  //means "insufficient number of array elements"
    }
    
    if (pa == 0)
    {
        *pn = 0;
        return E_POINTER;
    }
    
    IPin*& pin = pa[0];
    
    pin = &m_pFilter->m_outpin;
    pin->AddRef();
    
    *pn = 1;
    return S_OK;        
}


HRESULT Inpin::ReceiveConnection( 
    IPin* pin,
    const AM_MEDIA_TYPE* pmt)
{
    if ((pin == 0) || (pmt == 0))
        return E_POINTER;
        
    Filter::Lock lock;
        
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_state != State_Stopped)
        return VFW_E_NOT_STOPPED;

    if (m_connection)
        return VFW_E_ALREADY_CONNECTED;

    //assert(m_connection_mt.Empty());
    m_connection_mt.Clear();

    hr = QueryAccept(pmt);
    
    if (hr != S_OK)
        return VFW_E_TYPE_NOT_ACCEPTED;
        
    const AM_MEDIA_TYPE& mt = *pmt;
        
    hr = OnReceiveConnection(pin, mt);  //dispatch to subclass
    
    if (FAILED(hr))
        return hr;

    hr = m_connection_mt.Add(mt);
    
    if (FAILED(hr))
        return hr;
                    
    m_connection = pin;
    m_connection->AddRef();
    
    return S_OK;
}


HRESULT Inpin::EndOfStream()
{
    Filter::Lock lock;
        
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_connection == 0)
        return VFW_E_NOT_CONNECTED;
        
    if (m_pFilter->m_state == State_Stopped)
        return VFW_E_WRONG_STATE;  //?

    if (m_bEndOfStream)
        return S_FALSE;

    m_bEndOfStream = true;

    return OnEndOfStream();
}
    

HRESULT Inpin::BeginFlush()
{
    Filter::Lock lock;
        
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_connection == 0)
        return VFW_E_NOT_CONNECTED;
        
    //TODO: check state?
    
    //I think flush is supposed to flush EOS indicators
    //too (they can be queued, just like sammples).
    
    if (m_bFlush)
        return S_FALSE;
        
    m_bFlush = true;
        
    //TODO: we must tell file object to render the current cluster
    
    return S_OK;
}
    

HRESULT Inpin::EndFlush()
{
    Filter::Lock lock;
        
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_connection == 0)
        return VFW_E_NOT_CONNECTED;
        
    if (!m_bFlush)
        return S_FALSE;  //?

    m_bFlush = false;
    
    //TODO: it's not clear whether this should also clear
    //the EOS status.

    return S_OK;
}


HRESULT Inpin::NewSegment( 
    REFERENCE_TIME,
    REFERENCE_TIME,
    double)
{
    Filter::Lock lock;
        
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_connection == 0)
        return VFW_E_NOT_CONNECTED;
        
    //TODO: we could probably tell file object to render its
    //current cluster (and start a new one)
        
    return S_OK;  //TODO
}


HRESULT Inpin::GetAllocator(IMemAllocator** p)
{
    if (p)
        *p = 0;
        
    return VFW_E_NO_ALLOCATOR;  //?
}


HRESULT Inpin::NotifyAllocator( 
    IMemAllocator*,
    BOOL)
{
    return S_OK;
}


HRESULT Inpin::Receive(IMediaSample* pSample)
{
    if (pSample == 0)
        return E_INVALIDARG;

#if 0 //def _DEBUG
    {
        __int64 start_reftime, stop_reftime;
        const HRESULT hr = p->GetTime(&start_reftime, &stop_reftime);

        odbgstream os;
        os << "flvmux::outpin::enque_video: ";
        
        if (hr == S_OK)
            os << "start=" << double(start_reftime) / 10000000
               << "; stop=" << double(stop_reftime) / 10000000;

        else if (hr == VFW_S_NO_STOP_TIME)
            os << "start=" << double(start_reftime) / 10000000;

        os << endl;
    }
#endif

    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
        
    if (m_connection == 0)
        return VFW_E_NOT_CONNECTED;
    
    if (m_pFilter->m_state == State_Stopped)
        return VFW_E_WRONG_STATE;  //?

    if (m_bEndOfStream)
        return VFW_E_SAMPLE_REJECTED_EOS;

    if (m_bFlush)
        return S_FALSE;
      
    return OnReceive(pSample);
}


HRESULT Inpin::ReceiveMultiple(   //TODO: handle wait here, instead of in Receive?
    IMediaSample** pa,
    long n,    //in
    long* pm)  //out
{
    if (pm == 0)
        return E_POINTER;
        
    long& m = *pm;    //out
    m = 0;
    
    if (n <= 0)
        return S_OK;  //weird
    
    if (pa == 0)
        return E_INVALIDARG;
        
    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
        
    if (m_connection == 0)
        return VFW_E_NOT_CONNECTED;
    
    if (m_pFilter->m_state == State_Stopped)
        return VFW_E_WRONG_STATE;  //?

    if (m_bEndOfStream)
        return VFW_E_SAMPLE_REJECTED_EOS;

    if (m_bFlush)
        return S_FALSE;
      
    return OnReceiveMultiple(pa, n, m);
}


HRESULT Inpin::ReceiveCanBlock()
{
    return S_FALSE;  //no, we do not block caller
}



HRESULT Inpin::OnReceiveConnection(IPin*, const AM_MEDIA_TYPE&)
{
    return S_OK;
}


HRESULT Inpin::OnReceiveMultiple(IMediaSample* const* pSamples, long n, long& m)
{
    for (long i = 0; i < n; ++i)
    {
        IMediaSample* const pSample = pSamples[i];
        assert(pSample);
        
        const HRESULT hr = OnReceive(pSample);
        
        //We have to know whether to increment the count.
        //If there's an error, we don't increment the count.
        //We decide that S_FALSE means "did not process frame",
        //and hence we do not increment the count.
        
        if (hr != S_OK)  
            return S_FALSE;

        ++m;
    }
    
    return S_OK;
}


HRESULT Inpin::OnReceive(IMediaSample* pSample)
{   
    return m_pStream->Write(pSample);
}


HRESULT Inpin::OnEndOfStream()
{
    const int result = m_pStream->EndOfStream();
    
    if (result <= 0)
        return S_OK;
        
    return m_pFilter->OnEndOfStream();
}


}  //end namespace MkvMux
